package com.example.myyunpan.utils;

import com.example.myyunpan.entity.config.AppConfig;
import com.example.myyunpan.entity.constants.Constants;
import com.example.myyunpan.entity.domain.FileInfo;
import com.example.myyunpan.entity.dto.SessionWebUserDto;
import com.example.myyunpan.entity.enums.DateTimePatternEnum;
import com.example.myyunpan.entity.enums.FileStatusEnums;
import com.example.myyunpan.entity.enums.FileTypeEnums;
import com.example.myyunpan.entity.enums.ResponseCodeEnum;
import com.example.myyunpan.entity.vo.PaginationResultVO;
import com.example.myyunpan.exception.BusinessException;
import com.example.myyunpan.mapper.FileInfoMapper;
import org.apache.commons.io.FileUtils;
import org.mybatis.logging.Logger;
import org.mybatis.logging.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.function.Supplier;

@Component
public class MyFileUtils {

    @Value("${ffmpeg.cutvodieTS}")
    private String cutvodieTS;

    private static final Logger logger = LoggerFactory.getLogger(MyFileUtils.class);

    @Autowired
    private FileInfoMapper fileInfoMapper;

    @Autowired
    private AppConfig appConfig;

    public void readFile(HttpServletResponse response, String filePath){
//        检查路径是否合法
        if(!StringTools.pathIsOk(filePath)){
            return;
        }
        OutputStream out = null;
        FileInputStream fin = null;
        try {
            File file = new File(filePath);
            if(!file.exists()){
                return;
            }
            fin = new FileInputStream(file);
            byte[] byteData=new byte[1024];
            out=response.getOutputStream();
            int len = 0;
            while ((len = fin.read(byteData)) != -1) {
                out.write(byteData, 0, len);
            }
            out.flush();
        }catch (Exception e){
            return;
        }finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    logger.error(() -> "IO异常", e);
                }
            }
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    logger.error(() -> "IO异常", e);
                }
            }
        }

    }

    public void printNoDefaultImage(HttpServletResponse response) {
        response.setHeader(Constants.CONTENT_TYPE,Constants.CONTENT_TYPE_VALUE);
        response.setStatus(HttpStatus.OK.value());
        PrintWriter writer =null;
        try {
            writer = response.getWriter();
            writer.print("请在头像目录下放置默认头像default_avatar.jpg");
            writer.close();
        } catch (Exception e) {
            logger.error(()->"输出无默认图失败", e);
        } finally {
            writer.close();
        }
    }

    public  <S, T> PaginationResultVO<T> convert2PaginationVO(PaginationResultVO<S> result, Class<T> classz) {
        PaginationResultVO<T> resultVO = new PaginationResultVO<>();
        resultVO.setList(CopyTools.copyList(result.getList(), classz));
        resultVO.setPageNo(result.getPageNo());
        resultVO.setPageSize(result.getPageSize());
        resultVO.setPageTotal(result.getPageTotal());
        resultVO.setTotalCount(result.getTotalCount());
        return resultVO;
    }

    /**
     * TODO 合并分片文件
     * @param fileId
     * @param webUserDto
     */
    @Async
    public void transferFile(String fileId, SessionWebUserDto  webUserDto) {
        Boolean transferSuccess = true;
        String targetFilePath = null;
        String cover= null;
        FileTypeEnums fileType = null;
        FileInfo fileInfo =fileInfoMapper.selectByFileIdAndUserIdFileInfo(fileId, webUserDto.getUserId());
        try{
            if(fileInfo == null || !FileStatusEnums.TRANSFER.getStatus().equals(fileInfo.getStatus())){
                return;
            }
    //        临时目录
            String tempFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_TEMP;
            String currentUserFolderName = webUserDto.getUserId() + fileId;
            File tempFolder=new File(tempFolderName + currentUserFolderName);
            if(!tempFolder.exists()){
                tempFolder.exists();
            }
            //文件后缀
            String fileSuffix = StringTools.getFileSuffix(fileInfo.getFileName());
            String month = DateUtil.format(fileInfo.getCreateTime(), DateTimePatternEnum.YYYYMM.getPattern());
            //目标目录
            String targetFolderName = appConfig.getProjectFolder() + Constants.FILE_FOLDER_FILE;
            File targetFolder = new File(targetFolderName + "/" + month);
            if (!targetFolder.exists()) {
                targetFolder.mkdirs();
            }
    //        真实文件名
            String realFileName = currentUserFolderName + fileSuffix;
    //        真实文件路径
            targetFilePath = targetFolder.getPath() + "/" + realFileName;
            //合并文件
            union(tempFolder.getPath(), targetFilePath, fileInfo.getFileName(), true);
    //      视频文件切割，分片播放，将文件切成索引文件和ts切片文件，利用ffemge的cmd命令对音频文件进行切割
            fileType= FileTypeEnums.getFileTypeBySuffix(fileSuffix);
            if(fileType == FileTypeEnums.VIDEO){
    //            进行音频切割,生产视频缩略图
                cutFile4Video(fileId,targetFilePath);
//                视频播放，生成视频缩略图
                cover = month + "/" + currentUserFolderName + Constants.IMAGE_PNG_SUFFIX;
                String coverPath = targetFolderName + "/" +cover;
                ScaleFilter.createCover4Video(new File(targetFilePath), 200, new File(coverPath));
            }else if(fileType == FileTypeEnums.IMAGE){
    //            生产缩略图
                cover = month + "/" + realFileName.replace(".", "_.");
                String coverPath = targetFolderName + "/" +cover;
                Boolean iscreate = ScaleFilter.createThumbnailWidthFFmpeg(
                        new File(targetFilePath),
                        Constants.LENGTH_150,
                        new File(coverPath),false);
                 if(!iscreate){
                     FileUtils.copyFile(new File(targetFilePath),new File(coverPath));
                 }
            }
        } catch (Exception e) {
            transferSuccess = false;
            logger.error(() -> "合并文件失败", e);
        }finally {
//            最后将数据库数据更新一下，文件的大小，文件缩略图，文件状态
            FileInfo updateInfo = new FileInfo();
            updateInfo.setFileSize(new File(targetFilePath).length());
            updateInfo.setFileCover(cover);
            updateInfo.setStatus(transferSuccess ? FileStatusEnums.USING.getStatus() : FileStatusEnums.TRANSFER_FAIL.getStatus());
            fileInfoMapper.updateFileStatusWithOldStatus(fileId, webUserDto.getUserId(), updateInfo, FileStatusEnums.TRANSFER.getStatus());
        }

    }

    private void cutFile4Video(String fileId, String targetFilePath) {
        //创建同名切片目录
        File tsFolder = new File(targetFilePath.substring(0, targetFilePath.lastIndexOf(".")));
        if (!tsFolder.exists()) {
            tsFolder.mkdirs();
        }
        final String CMD_TRANSFER_2TS = "ffmpeg -y -i %s -vcodec copy -acodec copy -bsf:v h264_mp4toannexb %s";
        final String CMD_CUT_TS = "ffmpeg -i %s -c copy -map 0 -f segment -segment_list %s -segment_time "+cutvodieTS+" %s/%s_%%4d.ts";
        String tsPath = tsFolder + "/" + Constants.TS_NAME;
//        生产TS片段文件
        String cmd = String.format(CMD_TRANSFER_2TS, targetFilePath, tsPath);
        ProcessUtils.executeCommand(cmd, true);
//        生成索引文件.m3u8和切片.ts
        cmd = String.format(CMD_CUT_TS, tsPath, tsFolder.getPath()+"/"+Constants.M3U8_NAME, tsFolder.getPath(),fileId);
        ProcessUtils.executeCommand(cmd, true);
//        删除index.tx
        new File(tsPath).delete();
    }

    /**
     * TODO 合并文件
     * 获取目录下的所有文件：File fileList[] = dir.listFiles(); 获取指定目录 dirPath 下的所有文件。
     * 创建目标文件：File targetFile = new File(toFilePath); 创建目标文件对象。
     * 打开目标文件的写入流：writeFile = new RandomAccessFile(targetFile, "rw"); 打开目标文件的随机访问写入流。
     * 初始化缓冲区：byte[] b = new byte[1024 * 10]; 初始化一个10KB的缓冲区。
     * 遍历文件列表：for (int i = 0; i < fileList.length; i++) 遍历目录下的所有文件。
     * 创建当前文件的读取流：readFile = new RandomAccessFile(chunkFile, "r"); 打开当前文件的随机访问读取流。
     * 读取当前文件内容：while ((len = readFile.read(b)) != -1) 读取当前文件的内容到缓冲区。
     * 写入目标文件：writeFile.write(b, 0, len); 将缓冲区的内容写入目标文件。
     * 关闭当前文件的读取流：readFile.close(); 关闭当前文件的读取流。
     * 关闭目标文件的写入流：writeFile.close(); 关闭目标文件的写入流。
     * 结束：完成文件合并操作。
     * @param path 文件夹路径
     * @param targetFilePath 目标文件路径
     * @param fileName 文件名称
     * @param delSource
     */
    private void union(String path, String targetFilePath, String fileName, boolean delSource) {
        File dir = new File(path);
        if(!dir.exists()){
            throw  new BusinessException("目标目录不存在");
        }
        File[] fileList = dir.listFiles();
        if(fileList == null || fileList.length == 0){
            throw new BusinessException("目标目录为空");
        }
        File targetFile = new File(targetFilePath);
        RandomAccessFile writeFile = null;
        try{
            writeFile =new RandomAccessFile(targetFile, "rw");
            byte[] b = new byte[1024 * 10];
            for(int i = 0; i < fileList.length; i++){
                int len = -1;
                File chunkFile = new File(path + File.separator +i);
                RandomAccessFile readFile = null;
                try {
                    readFile = new RandomAccessFile(chunkFile, "r");
                    while ((len = readFile.read(b)) != -1) {
                        writeFile.write(b, 0, len);
                    }
                } catch (Exception e) {
                    logger.error(()->"合并文件:"+fileName+"失败", e);
                    throw new BusinessException("合并文件" + fileName + "出错了");
                }finally {
                    readFile.close();
                }
            }
        }catch (Exception e){
            logger.error(()->"合并文件:"+fileName+"失败", e);
            throw new BusinessException("合并文件" + fileName + "出错了");
        }finally {
            try {
                if (null != writeFile) {
                    writeFile.close();
                }
            } catch (IOException e) {
                logger.error(()->"关闭流失败", e);
            }
            if (delSource) {
                if (dir.exists()) {
                    try {
                        FileUtils.deleteDirectory(dir);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

}
